From ac3b2468589f22e78b72b664622df04697238288 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Mon, 21 Jun 2021 15:13:16 +0500 Subject: [PATCH] menubutton: Add a way to always show arrow with image buttons Make sure the button still has the .image-button style class with an icon, also add it to the initial state with only an arrow. Add a new .arrow-button style class for the icon+arrow state so it's possible to style it. Remove spacing from the label+arrow variant to match, re-add it from the stylesheet for both. Fixes https://gitlab.gnome.org/GNOME/gtk/-/issues/3501 --- gtk/gtkmenubutton.c | 163 ++++++++++++++++++++++++++++----- gtk/gtkmenubutton.h | 6 ++ gtk/theme/Default/_common.scss | 4 + 3 files changed, 152 insertions(+), 21 deletions(-) diff --git a/gtk/gtkmenubutton.c b/gtk/gtkmenubutton.c index 4a26b783fd..bce3e32eaf 100644 --- a/gtk/gtkmenubutton.c +++ b/gtk/gtkmenubutton.c @@ -66,6 +66,10 @@ * `GtkMenuButton` has a single CSS node with name `menubutton` * which contains a `button` node with a `.toggle` style class. * + * If the button contains only an icon or an arrow, it will have the + * `.image-button` style class, if it contains both, it will have the + * `.arrow-button` style class. + * * Inside the toggle button content, there is an `arrow` node for * the indicator, which will carry one of the `.none`, `.up`, `.down`, * `.left` or `.right` style classes to indicate the direction that @@ -85,6 +89,7 @@ #include "gtkactionable.h" #include "gtkbuiltiniconprivate.h" #include "gtkintl.h" +#include "gtkimage.h" #include "gtkmain.h" #include "gtkmenubutton.h" #include "gtkmenubuttonprivate.h" @@ -115,8 +120,10 @@ struct _GtkMenuButton GDestroyNotify create_popup_destroy_notify; GtkWidget *label_widget; + GtkWidget *image_widget; GtkWidget *arrow_widget; GtkArrowType arrow_type; + gboolean always_show_arrow; gboolean primary; }; @@ -133,6 +140,7 @@ enum PROP_DIRECTION, PROP_POPOVER, PROP_ICON_NAME, + PROP_ALWAYS_SHOW_ARROW, PROP_LABEL, PROP_USE_UNDERLINE, PROP_HAS_FRAME, @@ -168,6 +176,9 @@ gtk_menu_button_set_property (GObject *object, case PROP_ICON_NAME: gtk_menu_button_set_icon_name (self, g_value_get_string (value)); break; + case PROP_ALWAYS_SHOW_ARROW: + gtk_menu_button_set_always_show_arrow (self, g_value_get_boolean (value)); + break; case PROP_LABEL: gtk_menu_button_set_label (self, g_value_get_string (value)); break; @@ -207,6 +218,9 @@ gtk_menu_button_get_property (GObject *object, case PROP_ICON_NAME: g_value_set_string (value, gtk_menu_button_get_icon_name (GTK_MENU_BUTTON (object))); break; + case PROP_ALWAYS_SHOW_ARROW: + g_value_set_boolean (value, gtk_menu_button_get_always_show_arrow (self)); + break; case PROP_LABEL: g_value_set_string (value, gtk_menu_button_get_label (GTK_MENU_BUTTON (object))); break; @@ -411,6 +425,20 @@ gtk_menu_button_class_init (GtkMenuButtonClass *klass) NULL, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkMenuButton:always-show-arrow: (attributes org.gtk.Property.get=gtk_menu_button_get_always_show_arrow org.gtk.Property.set=gtk_menu_button_set_always_show_arrow) + * + * Whether to show a dropdown arrow even when using an icon. + * + * Since: 4.4 + */ + menu_button_props[PROP_ALWAYS_SHOW_ARROW] = + g_param_spec_boolean ("always-show-arrow", + P_("Always Show Arrow"), + P_("Whether to show a dropdown arrow even when using an icon"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** * GtkMenuButton:label: (attributes org.gtk.Property.get=gtk_menu_button_get_label org.gtk.Property.set=gtk_menu_button_set_label) * @@ -504,12 +532,48 @@ set_arrow_type (GtkWidget *arrow, gtk_widget_hide (arrow); } +static void +update_style_classes (GtkMenuButton *menu_button) +{ + if (menu_button->arrow_widget == gtk_button_get_child (GTK_BUTTON (menu_button->button)) || + (menu_button->image_widget && !menu_button->always_show_arrow)) + gtk_widget_add_css_class (menu_button->button, "image-button"); + else + gtk_widget_remove_css_class (menu_button->button, "image-button"); + + if (menu_button->image_widget && menu_button->always_show_arrow) + gtk_widget_add_css_class (menu_button->button, "arrow-button"); + else + gtk_widget_remove_css_class (menu_button->button, "arrow-button"); +} + +static void +update_arrow (GtkMenuButton *menu_button) +{ + gboolean has_only_arrow, is_text_button; + + if (menu_button->arrow_widget == NULL) + return; + + has_only_arrow = menu_button->arrow_widget == gtk_button_get_child (GTK_BUTTON (menu_button->button)); + is_text_button = menu_button->label_widget != NULL; + + set_arrow_type (menu_button->arrow_widget, + menu_button->arrow_type, + has_only_arrow || + ((is_text_button || menu_button->always_show_arrow) && + (menu_button->arrow_type != GTK_ARROW_NONE))); + + update_style_classes (menu_button); +} + static void add_arrow (GtkMenuButton *self) { GtkWidget *arrow; arrow = gtk_builtin_icon_new ("arrow"); + gtk_widget_set_halign (arrow, GTK_ALIGN_CENTER); set_arrow_type (arrow, self->arrow_type, TRUE); gtk_button_set_child (GTK_BUTTON (self->button), arrow); self->arrow_widget = arrow; @@ -524,6 +588,7 @@ gtk_menu_button_init (GtkMenuButton *self) gtk_widget_set_parent (self->button, GTK_WIDGET (self)); g_signal_connect_swapped (self->button, "toggled", G_CALLBACK (gtk_menu_button_toggled), self); add_arrow (self); + update_style_classes (self); gtk_widget_set_sensitive (self->button, FALSE); @@ -691,8 +756,6 @@ void gtk_menu_button_set_direction (GtkMenuButton *menu_button, GtkArrowType direction) { - gboolean is_image_button; - g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button)); if (menu_button->arrow_type == direction) @@ -701,14 +764,7 @@ gtk_menu_button_set_direction (GtkMenuButton *menu_button, menu_button->arrow_type = direction; g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_DIRECTION]); - /* Is it custom content? We don't change that */ - is_image_button = menu_button->label_widget == NULL; - if (is_image_button && (menu_button->arrow_widget != gtk_button_get_child (GTK_BUTTON (menu_button->button)))) - return; - - set_arrow_type (menu_button->arrow_widget, - menu_button->arrow_type, - is_image_button || (menu_button->arrow_type != GTK_ARROW_NONE)); + update_arrow (menu_button); update_popover_direction (menu_button); } @@ -841,6 +897,8 @@ void gtk_menu_button_set_icon_name (GtkMenuButton *menu_button, const char *icon_name) { + GtkWidget *box, *image_widget, *arrow; + g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button)); g_object_freeze_notify (G_OBJECT (menu_button)); @@ -848,7 +906,25 @@ gtk_menu_button_set_icon_name (GtkMenuButton *menu_button, if (gtk_menu_button_get_label (menu_button)) g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_LABEL]); - gtk_button_set_icon_name (GTK_BUTTON (menu_button->button), icon_name); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_halign (box, GTK_ALIGN_CENTER); + + image_widget = g_object_new (GTK_TYPE_IMAGE, + "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, + "icon-name", icon_name, + NULL); + menu_button->image_widget = image_widget; + + arrow = gtk_builtin_icon_new ("arrow"); + menu_button->arrow_widget = arrow; + + gtk_box_append (GTK_BOX (box), image_widget); + gtk_box_append (GTK_BOX (box), arrow); + gtk_button_set_child (GTK_BUTTON (menu_button->button), box); + + menu_button->label_widget = NULL; + + update_arrow (menu_button); g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_ICON_NAME]); @@ -868,7 +944,55 @@ gtk_menu_button_get_icon_name (GtkMenuButton *menu_button) { g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL); - return gtk_button_get_icon_name (GTK_BUTTON (menu_button->button)); + if (menu_button->image_widget) + return gtk_image_get_icon_name (GTK_IMAGE (menu_button->image_widget)); + + return NULL; +} + +/** + * gtk_menu_button_set_always_show_arrow: (attributes org.gtk.Method.set_property=always-show-arrow) + * @menu_button: a `GtkMenuButton` + * @always_show_arrow: hether to show a dropdown arrow even when using an icon + * + * Sets whether to show a dropdown arrow even when using an icon. + * + * Since: 4.4 + */ +void +gtk_menu_button_set_always_show_arrow (GtkMenuButton *menu_button, + gboolean always_show_arrow) +{ + g_return_if_fail (GTK_IS_MENU_BUTTON (menu_button)); + + always_show_arrow = !!always_show_arrow; + + if (always_show_arrow == menu_button->always_show_arrow) + return; + + menu_button->always_show_arrow = always_show_arrow; + + update_arrow (menu_button); + + g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_ALWAYS_SHOW_ARROW]); +} + +/** + * gtk_menu_button_get_always_show_arrow: (attributes org.gtk.Method.get_property=always-show-arrow) + * @menu_button: a `GtkMenuButton` + * + * Gets whether to show a dropdown arrow even when using an icon. + * + * Returns: whether to show a dropdown arrow even when using an icon + * + * Since: 4.4 + */ +gboolean +gtk_menu_button_get_always_show_arrow (GtkMenuButton *menu_button) +{ + g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), FALSE); + + return menu_button->always_show_arrow; } /** @@ -902,12 +1026,15 @@ gtk_menu_button_set_label (GtkMenuButton *menu_button, gtk_widget_set_halign (label_widget, GTK_ALIGN_CENTER); arrow = gtk_builtin_icon_new ("arrow"); menu_button->arrow_widget = arrow; - set_arrow_type (arrow, menu_button->arrow_type, menu_button->arrow_type != GTK_ARROW_NONE); gtk_box_append (GTK_BOX (box), label_widget); gtk_box_append (GTK_BOX (box), arrow); gtk_button_set_child (GTK_BUTTON (menu_button->button), box); menu_button->label_widget = label_widget; + menu_button->image_widget = NULL; + + update_arrow (menu_button); + g_object_notify_by_pspec (G_OBJECT (menu_button), menu_button_props[PROP_LABEL]); g_object_thaw_notify (G_OBJECT (menu_button)); @@ -924,16 +1051,10 @@ gtk_menu_button_set_label (GtkMenuButton *menu_button, const char * gtk_menu_button_get_label (GtkMenuButton *menu_button) { - GtkWidget *child; - g_return_val_if_fail (GTK_IS_MENU_BUTTON (menu_button), NULL); - child = gtk_button_get_child (GTK_BUTTON (menu_button->button)); - if (GTK_IS_BOX (child)) - { - child = gtk_widget_get_first_child (child); - return gtk_label_get_label (GTK_LABEL (child)); - } + if (menu_button->label_widget) + return gtk_label_get_label (GTK_LABEL (menu_button->label_widget)); return NULL; } diff --git a/gtk/gtkmenubutton.h b/gtk/gtkmenubutton.h index af71beedd6..390771f049 100644 --- a/gtk/gtkmenubutton.h +++ b/gtk/gtkmenubutton.h @@ -80,6 +80,12 @@ void gtk_menu_button_set_icon_name (GtkMenuButton *menu_button, GDK_AVAILABLE_IN_ALL const char * gtk_menu_button_get_icon_name (GtkMenuButton *menu_button); +GDK_AVAILABLE_IN_4_4 +void gtk_menu_button_set_always_show_arrow (GtkMenuButton *menu_button, + gboolean always_show_arrow); +GDK_AVAILABLE_IN_4_4 +gboolean gtk_menu_button_get_always_show_arrow (GtkMenuButton *menu_button); + GDK_AVAILABLE_IN_ALL void gtk_menu_button_set_label (GtkMenuButton *menu_button, const char *label); diff --git a/gtk/theme/Default/_common.scss b/gtk/theme/Default/_common.scss index a4db8c7301..dc9a9f6409 100644 --- a/gtk/theme/Default/_common.scss +++ b/gtk/theme/Default/_common.scss @@ -4322,6 +4322,10 @@ statusbar { } menubutton { + > button > box { + border-spacing: 6px; + } + arrow { min-height: 16px; min-width: 16px; -- 2.30.2